package com.derbysoft.lowcostadapter.dswitch2.service.impl;

import com.derbysoft.lowcostadapter.commons.model.Hotel;
import com.derbysoft.lowcostadapter.commons.model.RateCache;
import com.derbysoft.lowcostadapter.commons.model.RoomType;
import com.derbysoft.lowcostadapter.commons.redis.service.RateCacheService;
import com.derbysoft.lowcostadapter.commons.redis.service.RoomTypeCacheService;
import com.derbysoft.lowcostadapter.commons.redis.utils.KeyValueHelper;
import com.derbysoft.lowcostadapter.commons.utils.DateTimeUtils;
import com.derbysoft.lowcostadapter.dswitch2.service.GetCachedAvailService;
import com.derbysoft.lowcostadapter.dswitch2.service.RoomOccupancyService;
import com.derbysoft.lowcostadapter.dswitch2.service.RoomStayService;
import com.derbysoft.lowcostadapter.dswitch2.service.helper.Constant;
import com.derbysoft.lowcostadapter.dswitch2.service.helper.GuestCountUtils;
import com.derbysoft.lowcostadapter.dswitch2.translator.impl.MealBasis;
import com.derbysoft.lowcostadapter.dswitch3.service.AvailRequestMonitor;
import com.derbysoft.lowcostadapter.schedule.parse.ParseRateCacheService;
import com.derbysoft.remote.dswitch.core.*;
import com.derbysoft.remote.dswitch.generic.constants.UniqueIDType;
import com.derbysoft.remote.dswitch.generic.dto.SimpleAmountDTO;
import com.derbysoft.remote.dswitch.generic.dto.UniqueIDDTO;
import com.derbysoft.remote.dswitch.hotel.constants.FreeMealUnit;
import com.derbysoft.remote.dswitch.hotel.constants.MealsIncludedType;
import com.derbysoft.remote.dswitch.hotel.constants.PaymentType;
import com.derbysoft.remote.dswitch.hotel.constants.RateType;
import com.derbysoft.remote.dswitch.hotel.dto.*;
import com.derbysoft.remote.share.LanguageType;
import com.derbysoft.remote.share.dto.DateRangeDTO;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service("getCachedAvailService")
public class GetCachedAvailServiceImpl implements GetCachedAvailService {

    public static final int DELAY_ONE_DAY = -24;
    @Autowired
    private RateCacheService rateCacheRepository;

    @Autowired
    private RoomTypeCacheService roomTypeCacheService;

    @Autowired
    private RoomOccupancyService roomOccupancyService;

    @Autowired
    private RoomStayService<GenericRQ<HotelAvailRQ>, GenericRS<HotelAvailRS>> roomStayService;

    @Autowired
    private ParseRateCacheService parseRateCacheService;

    @Autowired
    private AvailRequestMonitor availRequestMonitor;

    @Override
    public GenericRS<HotelAvailRS> getCachedAvailability(GenericRQ<HotelAvailRQ> request, Hotel hotel) {
        availRequestMonitor.getQueryCacheCount().incrementAndGet();
        if (parseRateCacheService.isParsing()) {
            return null;
        }

        if (isRestricted(request, hotel)) {
            availRequestMonitor.getOccupancyRestrictedCount().incrementAndGet();
            return createEmptyResult(request);
        }

        List<RateCache> rateCaches = searchRateCaches(request, hotel);
        if (CollectionUtils.isEmpty(rateCaches)) {
            return null;
        }
        availRequestMonitor.getRateHitCount().incrementAndGet();
        return filterRoomStay(request, hotel, rateCaches);
    }

    private GenericRS<HotelAvailRS> filterRoomStay(GenericRQ<HotelAvailRQ> request, Hotel hotel, List<RateCache> rateCaches) {
        GenericRS<HotelAvailRS> filteredAvailRS = roomStayService.filter(request, createResponse(request, rateCaches), hotel);
        if (filteredAvailRS == null) {
            return null;
        }

        if (CollectionUtils.isEmpty(filteredAvailRS.getPayload().getHotelAvailRoomStay().getRoomStays())) {
            availRequestMonitor.getRoomStayFilteredCount().incrementAndGet();
        }

        filteredAvailRS.setSuccess(new SuccessDTO());
        return filteredAvailRS;
    }

    private GenericRS<HotelAvailRS> createResponse(GenericRQ<HotelAvailRQ> request, List<RateCache> rateCaches) {
        GenericRS<HotelAvailRS> response = new GenericRS<HotelAvailRS>();
        response.setHeader(createHeader(request.getHeader()));
        response.setPayload(createHotelAvailRS(rateCaches, request.getPayload().getAvailCriteria()));
        return response;
    }

    private boolean isRestricted(GenericRQ<HotelAvailRQ> request, Hotel hotel) {
        List<GuestCountDTO> guestCounts = request.getPayload().getAvailCriteria().getRoomStayCandidates().getGuestCounts();
        int adults = GuestCountUtils.getAdults(guestCounts);
        int children = GuestCountUtils.getChildren(guestCounts);
        Date checkIn = request.getPayload().getAvailCriteria().getStayDateRange().getCheckInDate();
        Date checkOut = DateTimeUtils.addHours(request.getPayload().getAvailCriteria().getStayDateRange().getCheckOutDate(), DELAY_ONE_DAY);
        return roomOccupancyService.isRestricted(hotel.getPropertyID(), adults, children, StayDateRange.build(checkIn, checkOut));
    }

    private HotelAvailRS createHotelAvailRS(GenericRQ<HotelAvailRQ> rq) {
        HotelAvailRS hotelAvailRS = new HotelAvailRS();
        hotelAvailRS.setHotelAvailRoomStay(createHotelAvailRoomStay(rq));
        return hotelAvailRS;
    }

    private HotelAvailRoomStayDTO createHotelAvailRoomStay(GenericRQ<HotelAvailRQ> rq) {
        HotelAvailRoomStayDTO hotelAvailRoomStayDTO = new HotelAvailRoomStayDTO();
        hotelAvailRoomStayDTO.setRoomStays(new ArrayList<AvailRoomStayDTO>());
        hotelAvailRoomStayDTO.setHotelRef(rq.getPayload().getAvailCriteria().getHotelRef());
        return hotelAvailRoomStayDTO;
    }

    private GenericRS<HotelAvailRS> createEmptyResult(GenericRQ<HotelAvailRQ> rq) {
        GenericRS<HotelAvailRS> rs = new GenericRS<HotelAvailRS>(createHeader(rq.getHeader()), createHotelAvailRS(rq));
        rs.setSuccess(new SuccessDTO());
        return rs;
    }

    private List<RateCache> searchRateCaches(GenericRQ<HotelAvailRQ> request, Hotel hotel) {
        int adult = request.getPayload().getAvailCriteria().getRoomStayCandidates().getAdultCount();
        int children = request.getPayload().getAvailCriteria().getRoomStayCandidates().getChildCount();
        String checkInDate = DateTimeUtils.formatDate(request.getPayload().getAvailCriteria().getStayDateRange().getCheckInDate());
        String key = RateCache.PREFIX + KeyValueHelper.SEPARATOR + hotel.getPropertyID() +
                KeyValueHelper.SEPARATOR + checkInDate + KeyValueHelper.SEPARATOR + adult + KeyValueHelper.SEPARATOR + children;
        return rateCacheRepository.loadAll(key);
    }

    private ResponseHeader createHeader(RequestHeader requestHeader) {
        return new ResponseHeader(requestHeader.getTaskId());
    }

    private HotelAvailRS createHotelAvailRS(List<RateCache> rateCaches, HotelAvailCriteriaDTO hotelAvailCriteriaDTO) {
        HotelAvailRS hotelAvailRS = new HotelAvailRS();
        hotelAvailRS.setHotelAvailRoomStay(createHotelAvailRoomStayDTO(rateCaches, hotelAvailCriteriaDTO));
        return hotelAvailRS;
    }

    private HotelAvailRoomStayDTO createHotelAvailRoomStayDTO(List<RateCache> rateCaches,
                                                              HotelAvailCriteriaDTO hotelAvailCriteriaDTO) {
        HotelAvailRoomStayDTO hotelAvailRoomStayDTO = new HotelAvailRoomStayDTO();
        hotelAvailRoomStayDTO.setHotelRef(hotelAvailCriteriaDTO.getHotelRef());

        hotelAvailRoomStayDTO.setRoomStays(createRoomStays(rateCaches, hotelAvailCriteriaDTO.getStayDateRange()));

        return hotelAvailRoomStayDTO;
    }

    private List<AvailRoomStayDTO> createRoomStays(List<RateCache> rateCaches,
                                                   StayDateRangeDTO stayDateRange) {
        Map<String, AvailRoomStayDTO> roomStayDTOMap = new LinkedHashMap<String, AvailRoomStayDTO>();

        for (RateCache rateCache : rateCaches) {
            String key = generatorRoomStayKey(rateCache);
            if (roomStayDTOMap.containsKey(key)) {
                continue;
            }
            roomStayDTOMap.put(key, createRoomStay(rateCache, stayDateRange));
        }

        List<AvailRoomStayDTO> availRoomStayDTOs = new ArrayList<AvailRoomStayDTO>();
        availRoomStayDTOs.addAll(roomStayDTOMap.values());
        return availRoomStayDTOs;
    }

    private String generatorRoomStayKey(RateCache rateCache) {
        return String.format("%d_%d", rateCache.getPropertyRoomTypeID(), rateCache.getMealBasis().getMealBaseID());
    }

    private AvailRoomStayDTO createRoomStay(RateCache rateCache, StayDateRangeDTO stayDateRangeDTO) {
        AvailRoomStayDTO availRoomStayDTO = new AvailRoomStayDTO();
        availRoomStayDTO.setRoomType(createRoomType(rateCache));
        availRoomStayDTO.setRatePlan(createRatePlan(rateCache));
        availRoomStayDTO.setRoomRate(createRoomRate(rateCache, stayDateRangeDTO));
        availRoomStayDTO.setLanguageType(LanguageType.EN_US);
        availRoomStayDTO.setProviderChain(createProviderChain());
        return availRoomStayDTO;
    }

    private ProviderChainDTO createProviderChain() {
        ProviderChainDTO providerChainDTO = new ProviderChainDTO();
        ArrayList<UniqueIDDTO> providers = new ArrayList<UniqueIDDTO>();
        providers.add(new UniqueIDDTO(Constant.LOW_COST_PASSPORT, UniqueIDType.WHOLESALER));
        providerChainDTO.setProviders(providers);
        return providerChainDTO;
    }

    private RoomRateDTO createRoomRate(RateCache rateCach, StayDateRangeDTO stayDateRangeDTO) {
        RoomRateDTO roomRateDTO = new RoomRateDTO();
        roomRateDTO.setRates(createRates(rateCach, stayDateRangeDTO));
        return roomRateDTO;
    }

    private List<RateDTO> createRates(RateCache rateCach, StayDateRangeDTO stayDateRangeDTO) {
        ArrayList<RateDTO> rateDTOs = new ArrayList<RateDTO>();
        rateDTOs.add(createRate(rateCach, stayDateRangeDTO));
        return rateDTOs;
    }

    private RateDTO createRate(RateCache rateCach, StayDateRangeDTO stayDateRangeDTO) {
        RateDTO rateDTO = new RateDTO();
        DateRangeDTO dateRangeDTO = createDateRange(stayDateRangeDTO);
        rateDTO.setDateRange(dateRangeDTO);
        rateDTO.setPureAmount(createPureAmount(rateCach));
        rateDTO.setMealsIncluded(createMealsIncluded());
        return rateDTO;
    }

    private SimpleAmountDTO createPureAmount(RateCache rateCache) {
        SimpleAmountDTO simpleAmountDTO = new SimpleAmountDTO();
        simpleAmountDTO.setAmount(rateCache.getRate());
        simpleAmountDTO.setCurrency(com.derbysoft.remote.share.Currency.GBP);
        return simpleAmountDTO;
    }

    private MealsIncludedDTO createMealsIncluded() {
        MealsIncludedDTO mealsIncludedDTO = new MealsIncludedDTO();
        mealsIncludedDTO.setMealsIncludedType(MealsIncludedType.UNKNOWN);
        return mealsIncludedDTO;
    }

    private DateRangeDTO createDateRange(StayDateRangeDTO stayDateRangeDTO) {
        DateRangeDTO dateRangeDTO = new DateRangeDTO();
        dateRangeDTO.setStart(stayDateRangeDTO.getCheckInDate());
        dateRangeDTO.setEnd(DateTimeUtils.offsetDays(stayDateRangeDTO.getCheckOutDate(), -1));
        return dateRangeDTO;
    }

    private RatePlanDTO createRatePlan(RateCache rateCache) {
        RatePlanDTO ratePlanDTO = new RatePlanDTO();
        ratePlanDTO.setCode(String.valueOf(rateCache.getMealBasis().getMealBaseID()));
        ratePlanDTO.setName(rateCache.getMealBasis().getDescription());
        ratePlanDTO.setFreeMeal(createFreeMeal(rateCache.getMealBasis().getMealBaseID()));
        ratePlanDTO.setRateType(RateType.PREPAY_NET_RATE);
        ratePlanDTO.setPaymentType(PaymentType.PREPAY);
        return ratePlanDTO;
    }

    private FreeMealDTO createFreeMeal(int mealBasisID) {
        FreeMealDTO freeMealDTO = new FreeMealDTO();
        MealBasis mealBasis = MealBasis.fromID(mealBasisID);
        freeMealDTO.setType(mealBasis.getFreeMealType());
        freeMealDTO.setUnit(FreeMealUnit.PER_PERSON);
        freeMealDTO.setDescription(mealBasis.getDescription());
        return freeMealDTO;
    }

    private RoomTypeDTO createRoomType(RateCache rateCache) {
        RoomTypeDTO roomTypeDTO = new RoomTypeDTO();
        roomTypeDTO.setCode(Integer.toString(rateCache.getPropertyRoomTypeID()));
        RoomType roomType = roomTypeCacheService.loadByRoomTypeId(rateCache.getPropertyRoomTypeID());
        roomTypeDTO.setName(roomType == null ? Integer.toString(rateCache.getPropertyRoomTypeID()) : roomType.getDescription());
        return roomTypeDTO;
    }
}
